package com.sicpa.common.utils;

import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;

/**
 * 签名加密工具类
 *
 * @author zhangjian
 * @date 2020/03/10 14:18
 */
public class SignUtils {

    /**
     * 组装接口参数
     *
     * @param dataParam
     * @param secret
     * @return
     */
    public static ObjectNode getParam(ObjectNode dataParam, String secret) {
        String applicationCode = dataParam.get("applicationCode").asText(null);
        // 时间戳 -> 公共参数用到
        long timestamp = Instant.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        // 构建待签名的参数
        String toSignStr = getStrToSign(timestamp, dataParam, applicationCode);

        // 生成签名, HMAC-SHA1加密
        HmacUtils hmacUtils = new HmacUtils(HmacAlgorithms.HMAC_SHA_1, secret);
        String sign = hmacUtils.hmacHex(toSignStr);

        // 构建最终请求参数，带上公共参数，将接口请求参数进行base64放入data字段
        ObjectNode finalParam = JsonUtil.getObjectMapper().createObjectNode();
        String dataJson = JsonUtil.toJson(dataParam);
        String encodedData = Base64.encodeBase64String(dataJson.getBytes(StandardCharsets.UTF_8));
        finalParam.put("applicationCode", applicationCode)
                .put("timestamp", timestamp)
                .put("data", encodedData)
                .put("signature", sign)
                .put("charset", StandardCharsets.UTF_8.toString());

        return finalParam;
    }

    /**
     * 获取待签名的字符串
     *
     * @param timestamp       时间戳
     * @param dataNode        请求参数
     * @param applicationCode 应用编码
     * @return 待签名字符串
     */
    private static String getStrToSign(long timestamp, ObjectNode dataNode, String applicationCode) {
        List<String> paramList = new ArrayList<>();
        // 添加公共参数
        paramList.add("timestamp=" + EncryptUtils.urlEncode(String.valueOf(timestamp)));
        paramList.add("applicationCode=" + EncryptUtils.urlEncode(applicationCode));
        paramList.add("charset=" + EncryptUtils.urlEncode(StandardCharsets.UTF_8.toString()));

        // 将接口参数以key=urlEncode(value)形式拼到待签名的参数列表
        dataNode.fieldNames().forEachRemaining(key -> {
            String value = dataNode.get(key).asText(null);

            if (value == null || value.length() == 0) {
                return;
            }

            try {
                paramList.add(key + "=" + URLEncoder.encode(value, StandardCharsets.UTF_8.toString()));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        });

        // 将参数列表按照字典进行升序排序
        paramList.sort(String::compareTo);

        // 最终参数以 key1=urlEncode(value1)&key2=urlEncode(value2) 这种形式，用 & 拼接成一个字符串，用于生成签名
        return String.join("&", paramList);
    }

}
